﻿#region Apache License Version 2.0
/*----------------------------------------------------------------

Copyright 2024 DJ Zheng & ShangHai GM Nahefa Technology Co.,Ltd.

Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
except in compliance with the License. You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software distributed under the
License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
either express or implied. See the License for the specific language governing permissions
and limitations under the License.

----------------------------------------------------------------*/
#endregion Apache License Version 2.0

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
//using System.Collections.PulseCommonHelper;
using Collector.Device;

namespace Collector
{
    class PulseThreeBodySubsection : PulseGeneralCollector
    {
        private readonly string TAG = "PulseThreeBodySubsection:";
        private readonly int MAXMIN_QUEUE_LENGTH = 1000;
        private readonly byte CMD_OFFSET = 0x05;
        private double mThresholdRadio = 0.0;
        // 记录目标压力值列表，进行分段加压 Unit: mmHg
        private List<int> mListTargetPressure = new List<int>();
        private ReaderWriterLockSlim mRWLock = new ReaderWriterLockSlim();
        private AutoResetEvent conditionWait = new AutoResetEvent(false);
        private IDeviceCommunication mDeviceCommunication;
        private SubSectionState mSubSectionState = SubSectionState.eSTATE_IDLE;
        // 存储当前分段稳定采集的有效压力值
        Queue<double> mPulsePressureQueue = new Queue<double>();
        private bool mUserRightHandSel = true;
        private string mPhoneNumber = "137xxxxxxxx";
        private string mSerialNumber = "snxxxxxxx";
        // 脉象数据保存的目录
        private string mRawDataPath;
        // 脉象原始数据保留文件
        private string mRawDataFile;

        public delegate int PulseContinuousNotifyDelegate<T>(T obj, PulseCommonHelper.Display.DisplayType type);
        public PulseContinuousNotifyDelegate<PulseCommonHelper.Display> notifyMainForm;

        // 脉象sensor寸关尺分别对应的静态压力
        private Dictionary<BodyPart, double> mThreeBodyPartBaseLineMap = new Dictionary<BodyPart, double>();

        // 脉象实时压力采样点，只用于实时显示脉压值
        private Dictionary<BodyPart, Queue<double>> mThreePluseDataMap = new Dictionary<BodyPart, Queue<double>>
        {
            {BodyPart.eBODY_PART_CUN, new Queue<double>()},
            {BodyPart.eBODY_PART_GUAN, new Queue<double>()},
            {BodyPart.eBODY_PART_CHI, new Queue<double>()}
        };

        private readonly Dictionary<int, BodyPart> mThreeBodyPartIndexMap = new Dictionary<int, BodyPart>
        {
            {0, BodyPart.eBODY_PART_CUN},
            {1, BodyPart.eBODY_PART_GUAN},
            {2, BodyPart.eBODY_PART_CHI}
        };

        private enum SubSectionState
        {
            // 已加压到目标值
            eSTATE_REACHED,
            // 已加压到目标值进行稳定采样中
            eSTATE_STABLE_COLLECTING,
            // 目标值已经采集完成
            eSTATE_FINISHED,
            // 设备空闲, 此时设备应该是复位状态
            eSTATE_IDLE
        }

        /// <summary>
        /// @param baseline 基线压力 mBar
        /// @param targetPressureList 目标压力值 mmHg
        /// </summary>
        /// <param name="baseline"></param>
        public PulseThreeBodySubsection(Dictionary<BodyPart, double> baseLineMap, List<int> targetPressureList)
        {
            foreach(var key in baseLineMap.Keys)
            {
                mThreeBodyPartBaseLineMap.Add(key, baseLineMap[key]);
            }
            targetPressureList.ForEach(item => mListTargetPressure.Add(item));
        }

        public double ThresholdRadio
        {
            get { return mThresholdRadio; }
            set { mThresholdRadio = value; }
        }

        public IDeviceCommunication DeviceCommunication
        {
            get { return mDeviceCommunication;  }
            set { DeviceAdapter = mDeviceCommunication = value; }
        }

        public bool UserRightHandSel
        {
            get { return mUserRightHandSel; }
            set { mUserRightHandSel = value; }
        }

        public string PhoneNumber
        {
            get { return mPhoneNumber; }
            set { mPhoneNumber = value; }
        }

        public string SerialNumber
        {
            get { return mSerialNumber; }
            set { mSerialNumber = value; }
        }

        public string RawDataPath
        {
            get { return mRawDataPath; }
            set { mRawDataPath = value; }
        }

        public bool ResetDevice()
        {            
            return DeviceAdapter.ResetDevice();
        }

        public bool DoSubsectionProcessing(bool isAir = false)
        {
            bool ret = false;

            // 检查加压段数，至少有一段
            if (mListTargetPressure.Count == 0)
            {
                Log.Error(TAG + "DoSubsectionProcessing() target pressure list is null");
                return false;
            }

            // 执行加压段数目标阈值
            for (int i = 0; i < mListTargetPressure.Count; i++)
            {
                byte[] payload;
                // Convert mmHg to Pa
                int targetPressure = (int)(mListTargetPressure[i] * 133.33);
                DisplayMessageToUI(string.Format("分段加压 {0}mmHg,Waiting for {1}S...", mListTargetPressure[i], (MAXMIN_QUEUE_LENGTH / 100)), LogMsgType.DEBUG);

                // Reset pulse pressure queue.
                mPulsePressureQueue.Clear();
                foreach(var kvt in mThreePluseDataMap)
                {
                    kvt.Value.Clear();
                }

                // Reset pulse subsection state
                Log.Info(TAG + "DoSubsectionProcessing() change state to eSTATE_IDLE");
                setSubsectionState(SubSectionState.eSTATE_IDLE);

                // 创建采集数据的存储文件
                mRawDataFile = CreateSaveRawDataFile();

                // 发送分段加压指令
                if(!isAir)
                    ret = setTargetPrussure(targetPressure);
                else
                    ret = setTargetPrussureForAir(targetPressure);
                if (!ret)
                    return ret;

                // 等待脉压数据收集完成
                ret = waitTargetPressureReached();
                if (!ret)
                    break;

                if (pulseDataProcessing(mListTargetPressure[i]) == false)
                    break;
            }

            return ret;
        }

        private bool setTargetPrussure(int pressure)
        {
            byte[] payload;
            // 获取加压指令头部
            byte[] cmdHeader = FileConfig.getSerialPortData("SPHeadmmHgData");
            // +2 是分配目标压力值2字节空间
            byte[] final_byteCmd = new byte[cmdHeader.Length + 2];
            cmdHeader.CopyTo(final_byteCmd, 0);
            // padding target High byte
            final_byteCmd[final_byteCmd.Length-2] = (byte)((pressure >> 8) & 0xFF);
            // Padding target Low byte
            final_byteCmd[final_byteCmd.Length-1] = (byte)(pressure & 0xFF);
            //Log.Debug(TAG + "setTargetPrussure:SendCmd_mmHg :" + string.Format("{0}", string.Join(" ", final_byteCmd.Select(p => string.Format("{0:X2}", p)))));
            bool ret = mDeviceCommunication.SendCommandToDevice(final_byteCmd, "SPHeadmmHgData", 0x92, out payload);
            if (ret && payload[0] != 0x00)
            {
                ret = false;
                Log.Error(TAG + "setTargetPrussure() request the device failed to increase pressure");               
            }

            return ret;
        }

        private bool setTargetPrussureForAir(int pressure)
        {
            byte[] payload;
            // 获取加压指令头部
            byte[] cmdHeader = FileConfig.getSerialPortData("SPHeadmmHgData");
            // +2 是分配目标压力值2字节空间
            byte[] final_byteCmd = new byte[cmdHeader.Length + 2];
            cmdHeader.CopyTo(final_byteCmd, 0);
            // padding target High byte
            final_byteCmd[final_byteCmd.Length - 2] = (byte)((pressure >> 8) & 0xFF);
            // Padding target Low byte
            final_byteCmd[final_byteCmd.Length - 1] = (byte)(pressure & 0xFF);
            final_byteCmd[5] = 0xf1;
            //Log.Debug(TAG + "setTargetPrussure:SendCmd_mmHg :" + string.Format("{0}", string.Join(" ", final_byteCmd.Select(p => string.Format("{0:X2}", p)))));
            bool ret = mDeviceCommunication.SendCommandToDevice(final_byteCmd, "SPHeadmmHgData", 0xf1, out payload);
            if (ret && payload[0] != 0x00)
            {
                ret = false;
                Log.Error(TAG + "setTargetPrussure() request the device failed to increase pressure");
            }

            return ret;
        }

        private bool waitTargetPressureReached()
        {
            // 默认已经检测到加压到位
            bool ret = true;
            SubSectionState state = getSubsectionState();
            try
            {
                // 检查是否加压已经到位，否则等待
                if (state != SubSectionState.eSTATE_REACHED)
                {
                    Log.Info(TAG + "waitTargetPressureReached() Wait STATE_REACHED");
                    // 等待加压到位，等待10s
                    ret = conditionWait.WaitOne(25 * 1000);
                }
            }
            finally
            {
                // 等待稳定脉压数据收集
                if(ret)
                {
                    Log.Info(TAG + "waitTargetPressureReached() change state from STATE_REACHED to STATE_STABLE_COLLECTING");
                    DisplayMessageToUI("目标加压完成，等待10s数据采集中...", LogMsgType.INFO);
                    setSubsectionState(SubSectionState.eSTATE_STABLE_COLLECTING);
                    ret = conditionWait.WaitOne(15 * 1000);
                    if(ret)
                    {
                        Log.Info(TAG + "waitTargetPressureReached() change state from STATE_STABLE_COLLECTING to STATE_FINISHED");
                        DisplayMessageToUI("数据收集完成", LogMsgType.INFO);
                    } 
                    else
                    {
                        Log.Error(TAG + "waitTargetPressureReached() wait state of STATE_FINISHED is failed");
                    }
                }
            }

            return ret;
        }

        /**
         * 处理收集的脉压数据。
         * 检查脉压数据压力范围，均值、最大均值、最小均值、最大最小均值差
         * 
         * @param targetValue 当前分段目标压力值 mmHg
         */
        private bool pulseDataProcessing(int targetValue /*mmHg*/)
        {
            // Unit: Pa
            double pulse_raw_data_average = 0.0;
            // Unit: mmHg 减去基线压力值
            double pulse_data_without_baseline_average = 0.0;
            double takeData_Last = 0.0;
            double takeData_First = 0.0;
            double diffpPValue = 0.0;

            DisplayMessageToUI("MaxMin_WaveData.Count :" + mThreePluseDataMap[BodyPart.eBODY_PART_CUN].Count, LogMsgType.INFO);
            if(mThreePluseDataMap[BodyPart.eBODY_PART_CUN].Count != MAXMIN_QUEUE_LENGTH)
            {
                DisplayMessageToUI(string.Format("脉压数据采集有误 {0}", mThreePluseDataMap[BodyPart.eBODY_PART_CUN].Count), LogMsgType.ERROR);
                return false;
            }

            // 计算有效脉压数据的平均值
            foreach (var item in mThreeBodyPartIndexMap)
            {
                pulse_raw_data_average = Math.Round(mThreePluseDataMap[item.Value].Average(), 2);
                pulse_data_without_baseline_average = Math.Round(((pulse_raw_data_average - mThreeBodyPartBaseLineMap[item.Value])) / 133.33, 2);
                DisplayMessageToUI(string.Format("MinValue:{0}mmHg, CurValue:{1}mmHg",
                        (targetValue * (1 - mThresholdRadio)), pulse_data_without_baseline_average), LogMsgType.INFO);

                // 1000脉压数据,取前10位平均值
                takeData_First = Math.Round((mThreePluseDataMap[item.Value].Take(10).Average() - mThreeBodyPartBaseLineMap[item.Value]) / 133, 2);
                // 1000脉压数据,取最后10位平均值
                takeData_Last = Math.Round((mThreePluseDataMap[item.Value].Reverse().Take(10).Average() - mThreeBodyPartBaseLineMap[item.Value]) / 133, 2);
                diffpPValue = Math.Round((takeData_First - takeData_Last), 2);
                
                Log.Debug(TAG + string.Format("averagepPValue_mmHg:{0}->{1}, diff_mmHg:{2}->{3}, FrontValue :{4}, LastValue :{5}",
                        pulse_data_without_baseline_average, targetValue, diffpPValue, mThresholdRadio, 
                        takeData_First, takeData_Last), LogMsgType.INFO);
#if false
                if (pulse_data_without_baseline_average > (targetValue * (1 - mThresholdRadio)))
                {
                    Log.Debug(TAG + "pulseDataProcessing PASSED");
                    DisplayMessageToUI(string.Format("分段加压 {0}mmHg PASS", targetValue), LogMsgType.INFO);
                    DisplayMessageToUI("分段加压测试", string.Format("{0}mmHg 加压测试 PASS", targetValue), true);
                    return true;
                }
                else
                {
                    Log.Debug(TAG + "pulseDataProcessing FAILED");
                    DisplayMessageToUI(string.Format("分段加压 {0}mmHg FAIL", targetValue), LogMsgType.ERROR);
                    DisplayMessageToUI("分段加压测试", string.Format("{0}mmHg 加压测试 FAIL", targetValue), false);
                    return false;
                }
#endif
            }

            return true;
        }

        private void setSubsectionState(SubSectionState state, bool needRelease = false)
        {
            mRWLock.TryEnterWriteLock(200);
            try
            {
                if (mSubSectionState != state)
                {
                    mSubSectionState = state;
                    // 通知状态更新
                    if (needRelease)
                        conditionWait.Set();
                }
            }
            finally
            {
                mRWLock.ExitWriteLock();
            }
        }

        private SubSectionState getSubsectionState()
        {
            SubSectionState state = SubSectionState.eSTATE_IDLE;
            mRWLock.TryEnterReadLock(200);
            state = mSubSectionState;
            mRWLock.ExitReadLock();

            return state;
        }

        public static bool ByteToFile(byte[] byteArray, string fileName)
        {
            bool result = false;
            byte[] padding = { 0x00, 0x00, 0x00, 0x00, 0x00 };
            try
            {
                using (FileStream fs = new FileStream(fileName, FileMode.Append, FileAccess.Write))
                {
                    fs.Write(byteArray, 0, byteArray.Length);
                    //fs.Write(padding, 0, 5);
                    result = true;
                }
            }
            catch
            {
                result = false;
            }
            return result;
        }

        public override void ControlFrameProcessing(byte[] frame)
        {
            
            DeviceEvent evt = (DeviceEvent)frame[IDeviceProtocolShape.FRAME_FIELD_CMD_OFFSET];
            Log.Debug(TAG + "ControlFrameProcessing() {0} evt code: {1}", ConvertFuncs.ByteArrayToHexString(frame), evt);
            switch (evt)
            {
                case DeviceEvent.eDEV_EVENT_REACHED:
                {
                    DeviceSegmentRespondCode code = (DeviceSegmentRespondCode)frame[IDeviceProtocolShape.FRAME_FIELD_PAYLOAD_OFFSET];
                    if(code != DeviceSegmentRespondCode.eDEV_SEGMENT_ACK_CODE_COMPLETED)
                    {
                        // Log.Debug(TAG + "ControlFrameProcessing() ");
                        ///TODO: should to processing timeout task
                        break;
                    }
                       
                    // 回复设备收到A7事件
                    if (mDeviceCommunication != null)
                    {
                        mDeviceCommunication.GivebackCompleted(evt);
                    }

                    // 更新状态
                    if(getSubsectionState() != SubSectionState.eSTATE_STABLE_COLLECTING)
                        setSubsectionState(SubSectionState.eSTATE_REACHED, true);
                        
                }
                break;
                case DeviceEvent.eDEV_EVENT_OVERFLOW:
                {

                }
                break;
                case DeviceEvent.eDEV_EVENT_RESET:
                {
                    // 回复设备收到A4事件
                    if (mDeviceCommunication != null)
                    {
                        mDeviceCommunication.GivebackCompleted(evt);
                    }
                    
                    // Log.Debug(TAG + "ControlFrameProcessing() device already reset completed");
                    // 更新状态
                    setSubsectionState(SubSectionState.eSTATE_IDLE);
                }
                break;
                default:
                {
                    base.ControlFrameProcessing(frame);
                }
                break;
            }                       
        }

        /**
         * 解析一帧有效的脉压帧数据
         * 脉压帧格式:
         * ---------------------------------------------------------------------------------------------------------------------------------------------------------------
         * |              |                    |                                                       Payload(12Byte)                                                   |
         * | Header(4Byte)| PayloadSize(1Byte) |--------------------------------------------------------------------------------------------------------------------------
         * |              |                    |           snr0(3Byte)        |           snr1(3Byte)        |           snr2(3Byte)        | SnrType |     Seq(2Byte)   |
         * ---------------------------------------------------------------------------------------------------------------------------------------------------------------
         * | BB 66 BB 66  |       0x0C         | H(1Byte)| M(1Byte)| L(1Byte) | H(1Byte)| M(1Byte)| L(1Byte) | H(1Byte)| M(1Byte)| L(1Byte) |   0x04  | L(1Byte)|H(1Byte)|
         * ---------------------------------------------------------------------------------------------------------------------------------------------------------------
         * 
         * @param frame 一帧有效的脉压帧数据
         * 
         */
        public override void PulseFrameProcessing(byte[] frame) 
        {
            //Log.Debug("==>frame: " + ConvertFuncs.ByteArrayToHexString(frame) + ", size:{0} ", frame.Length);
            SubSectionState state = getSubsectionState();
            if (state != SubSectionState.eSTATE_STABLE_COLLECTING)
            {
                return;
            }

            byte payload_size = frame[IDeviceProtocolShape.FRAME_FIELD_LENGTH_OFFSET];
            int end_pos = 4 + (int)payload_size;
            byte sensorType = frame[end_pos - 2];

            ///
            /// TODO: Should to check frame size to make sure frame is availd.
            ///
            foreach(var item in mThreeBodyPartIndexMap)
            {
                int i = item.Key;
                int d = (frame[IDeviceProtocolShape.FRAME_FIELD_PULSE_DATA_L + i * 3]) |
                        (frame[IDeviceProtocolShape.FRAME_FIELD_PULSE_DATA_M + i * 3] << 8) |
                        (frame[IDeviceProtocolShape.FRAME_FIELD_PULSE_DATA_H + i * 3] << 16);
                double ratio = sensorType == 3 ? 32 : 1;
                // Unit: Pa
                double pPData = d / ratio;

                var part = item.Value;
                mThreePluseDataMap[part].Enqueue(pPData);
                if (mThreePluseDataMap[part].Count >= MAXMIN_QUEUE_LENGTH)
                    // 收集有效的脉压数据已经完成
                    setSubsectionState(SubSectionState.eSTATE_FINISHED, true);                
            }

            // 保存有效数据
            if(mRawDataFile != null)
                ByteToFile(frame, mRawDataFile);
        }

        public override void AirFrameProcessing(byte[] frame) { }

        /// <summary>
        /// 非阻塞线程，通知显示UI
        /// </summary>
        /// <param name="message"></param>
        /// <param name="type"></param>
        private void DisplayMessageToUI(PulseCommonHelper.Display display, PulseCommonHelper.Display.DisplayType type)
        {
            if (notifyMainForm != null)
            {
                notifyMainForm(display, type);
            }
        }

        private void DisplayMessageToUI(string message, LogMsgType type)
        {
            PulseCommonHelper.Display display = new PulseCommonHelper.Display();
            display.displayMsgBox.message = message;
            display.displayMsgBox.type = type;
            if (notifyMainForm != null)
            {
                notifyMainForm(display, PulseCommonHelper.Display.DisplayType.eDISPLAY_TYPE_MSG);
            }
        }

        private void DisplayMessageToUI(string itemName, string itemInfo, bool result)
        {
            PulseCommonHelper.Display display = new PulseCommonHelper.Display();
            display.displayRltBox.itemName = itemName;
            display.displayRltBox.itemInfo = itemInfo;
            display.displayRltBox.result = result;
            if (notifyMainForm != null)
            {
                notifyMainForm(display, PulseCommonHelper.Display.DisplayType.eDISPLAY_TYPE_ITEM);
            }
        }

        private string CreateSaveRawDataFile()
        {
            if (mRawDataPath == null)
                return null;

            string strdate = DateTime.Now.ToString("yyyyMMdd");
            string str_hms = DateTime.Now.TimeOfDay.ToString("hhmmssfff");
            string logFileName = mRawDataPath + "\\" + String.Format("{0}-{1}-{2}-{3}-{4}-{5}-{6}-{7}-{8}-{9}.dat",
                "GMMX", mPhoneNumber, "3", mSerialNumber.Substring(4),
                strdate, str_hms,
                mThreeBodyPartBaseLineMap[BodyPart.eBODY_PART_CUN],
                mThreeBodyPartBaseLineMap[BodyPart.eBODY_PART_GUAN],
                mThreeBodyPartBaseLineMap[BodyPart.eBODY_PART_CHI],
                mUserRightHandSel ? "R" : "L");

            Log.Debug(TAG + "CreateSaveRawDataFile() Save file path: " + logFileName);
            return logFileName;
        }
    }
}
